﻿using System;
using System.Diagnostics;

namespace Calculator
{
    [ImportClass("", "Core")]
    public class Core
    {
        private int _radix = 10;
        private string _angleMode = Constant.DEG;
        private string _operator;
        private bool _hasMemory = false;
        private double _memory = 0;
        private double _current = 0;
        private string _input = "";
        private double _register;
        private int _status = Status.Start;

        public int Radix
        {
            set
            {
                this._register = 0;
                this._operator = "";
                this._status = Status.Return;

                this._radix = value;
                this._current = Math.Round(this._current);
                try
                {
                    this.ValidateResult(this._current);
                }
                catch
                {
                    this._status = Status.Error;
                }
            }
        }

        public string AngleMode
        {
            set
            {
                this._angleMode = value;
                //TODO:angle change
            }
        }

        public bool HasError
        {
            get
            {
                return this._status == Status.Error;
            }
        }

        public bool HasMemory
        {
            get { return this._hasMemory; }
        }

        public double MemoryData
        {
            get { return this._memory; }
        }

        private string InternalResult
        {
            get
            {
                if (this._status != Status.Number)
                    return Global.Number(this._current.ToFixed(16)).ToString();
                else
                {
                    if (this._radix == 10)
                        return this._input;//Note:Must return string -- otherwise if you input 0.005, it always show 0 until you input 5
                    else
                        return Global.ParseInt(this._input, this._radix).ToString();
                }
            }
        }

        public string Result
        {
            get
            {
                if (this._radix == 10)
                {
                    return this.InternalResult;
                }
                else
                {
                    return Global.ParseInt(this.InternalResult).ToString(this._radix).ToUpperCase();
                }
            }
        }

        private double InternalRegister
        {
            get
            {
                return Global.Number(this._register.ToFixed(16));
            }
        }

        public string Register
        {
            get
            {
                if (this._radix == 10)
                {
                    return this.InternalRegister.ToString();
                }
                else
                {
                    return this.InternalRegister.ToString(this._radix).ToUpperCase();
                }
            }
        }

        public string Operator
        {
            get
            {
                return this._operator;
            }
        }

        public void InputNumber(string val)
        {
            if (this._status == Status.Error)
                return;
            //limit the length user can input
            if (this._input.Length > 20)
                return;

            switch (this._status)
            {
                case Status.Start:
                case Status.Return:
                    this._input = val;
                    this._status = Status.Number;
                    break;

                case Status.Number:
                    this._input += val;
                    break;

                case Status.Wait:
                    this._register = this._current;
                    this._input = val;
                    this._status = Status.Number;
                    break;
            }

            if (this._radix == 10)
            {
                this._current = Global.Number(this._input);
            }
            else
            {
                this._current = Global.ParseInt(this._input.ToLowerCase(), this._radix);
            }

            var allZero = true;
            for (var i = 0; i < this._input.Length; i++)
            {
                if (this._input[i] != '0')
                    allZero = false;
            }
            if (allZero)
                this._input = "0";
            if (this._input.Length > 1 && this._input[0] == '0' && this._input[1] != '.')//Remove lead zero
                this._input = this._input.Substr(1);
        }

        public void InputDecimal()
        {
            switch (this._status)
            {
                case Status.Error:
                    return;

                case Status.Start:
                case Status.Return:
                    this._input = "0.";
                    this._status = Status.Number;
                    break;

                case Status.Number:
                    if (this._input.IndexOf(".") != -1)
                        return;
                    else
                        this._input += '.';
                    break;

                case Status.Wait:
                    this._input = "0.";
                    this._status = Status.Number;
                    break;
            }
            this._current = Global.Number(this._input);
        }

        public void InputReset()
        {
            this._register = 0;
            this._current = 0;
            this._operator = "";
            this._status = Status.Start;
            this._input = "";
        }

        public void InputBack()
        {
            if (this._status == Status.Number)//Only valid when inputting number
            {
                if (this._input.Length <= 1)
                {
                    this._input = "0";
                    this._current = 0;
                }
                else
                {
                    this._input = this._input.Substr(0, this._input.Length - 1);
                    this._current = Global.Number(this._input);
                }
            }
        }

        public void InputOperator(string val)
        {
            if (val == "x<sup>y</sup>")
                val = "^";

            switch (this._status)
            {
                case Status.Error:
                    return;

                case Status.Start:
                case Status.Return:
                    this._register = this._current;
                    this._operator = val;
                    this._status = Status.Wait;
                    break;

                case Status.Number:
                    if (this._operator != null && this._operator != "")
                    {
                        try
                        {
                            this.Calc();
                        }
                        catch (Exception)
                        {
                            this._status = Status.Error;
                            return;
                        }
                    }
                    this._register = this._current;
                    this._operator = val;
                    this._status = Status.Wait;
                    break;

                case Status.Wait:
                    this._operator = val;
                    break;
            }
        }

        public void InputEqual()
        {
            if (this._status == Status.Error)
                return;

            if (this._status == Status.Number || this._status == Status.Wait || this._status == Status.Return)
            {
                if (this._operator == null || this._operator == "")
                    return;

                try
                {
                    this.Calc();
                    this._register = 0;
                    this._operator = "";
                    this._status = Status.Start;
                    this._input = this._current.ToString();
                }
                catch (Exception)
                {
                    this._status = Status.Error;
                }
            }
        }

        public void InputPercent()
        {
            if (this._status == Status.Wait || this._status == Status.Error)
                return;

            this._current = this._current / 100;
            this._input = this._current.ToString();
            this._status = Status.Return;
        }

        private void Calc()
        {
            double r = 0;
            switch (this._operator)
            {
                case "+":
                    r = this._register + this._current;
                    break;

                case "-":
                    r = this._register - this._current;
                    break;

                case "×":
                    r = this._register * this._current;
                    break;

                case "÷":
                    r = this._register / this._current;
                    break;

                case "^":
                    r = Math.Pow(this._register, this._current);
                    break;
            }
            if (this._radix != 10)
            {
                r = Math.Round(r);
            }
            this._current = r;

            this.ValidateResult(r);
        }


        private void ValidateResult(double r)
        {
            if (Global.IsNaN(r))
                throw new Exception("NaN");
            if (!Global.IsFinite(r))
                throw new Exception("Inf");

            switch (this._radix)
            {
                case 2:
                    if (this._current > Math.Pow(2, 32) || this._current < -Math.Pow(2, 32))
                        throw new Exception("Ovf");
                    break;

                case 8:
                    if (this._current > Math.Pow(8, 32) || this._current < -Math.Pow(8, 32))
                        throw new Exception("Ovf");
                    break;

                case 16:
                    if (this._current > Math.Pow(16, 32) || this._current < -Math.Pow(16, 32))
                        throw new Exception("Ovf");
                    break;
            }
        }

        public void InputSign()
        {
            if (this._status == Status.Wait || this._status == Status.Error)
                return;
            var ch = this._input[0];
            if (ch == '-')
                this._input = this._input.Substr(1);
            else
                this._input = '-' + this._input;
            this._current = Global.Number(this._input);
        }

        public void InputConstant(string val)
        {
            if (this._status == Status.Error)
                return;

            switch (val)
            {
                case "π":
                    this._current = Math.PI;
                    break;

                case "e":
                    this._current = Math.E;
                    break;
            }

            this._input = this._current.ToString();
            this._status = Status.Return;
        }

        public void InputMemory(string val)
        {
            if (this._status == Status.Error)
                return;

            //TODO:possible overflow error in M+/M-
            switch (val)
            {
                case "MC":
                    this._hasMemory = false;
                    this._memory = 0;
                    this._status = Status.Wait;
                    break;

                case "M+":
                    this._memory += this._current;
                    this._hasMemory = true;
                    this._status = Status.Wait;
                    break;

                case "M-":
                    this._memory -= this._current;
                    this._hasMemory = true;
                    this._status = Status.Wait;
                    break;

                case "MR":
                    if (this._hasMemory)
                    {
                        this._current = this._memory;
                        this._status = Status.Return;
                        return;
                    }
                    break;
            }
            try
            {
                this.ValidateResult(this._memory);
            }
            catch (Exception)
            {
                this._status = Status.Error;
            }
        }

        public void InputTrigonometric(string val, bool isInv, bool isHyp)
        {
            if (!isInv && !isHyp)
            {
                switch (this._angleMode)
                {
                    case Constant.DEG:
                        this._current = Math.PI * this._current / 180;
                        break;
                    case Constant.RAD://Rad, do nothing
                        break;
                    case Constant.GRAD:
                        this._current = Math.PI * this._current / 200;
                        break;
                }
            }

            string name = val;
            if (isInv)
                name = "a" + name;
            if (isHyp)
                name += "h";

            try
            {
                this._current = (Shadow.Math[name] as Func<double, double>)(this._current);

                if (isInv && !isHyp)
                {
                    switch (this._angleMode)
                    {
                        case Constant.DEG:
                            this._current = this._current * 180 / Math.PI;
                            break;
                        case Constant.RAD://Rad, do nothing
                            break;
                        case Constant.GRAD:
                            this._current = this._current * 200 / Math.PI;
                            break;
                    }
                }

                this._input = this._current.ToString();
                this._status = Status.Return;
                this.ValidateResult(this._current);
            }
            catch
            {
                this._status = Status.Error;
            }
        }

        public void InputFunction(string func)
        {
            Console.Log(func);
            if (this._status == Status.Wait || this._status == Status.Error)
                return;

            try
            {
                this._current = (Shadow.Math[func] as Func<double, double>)(this._current);
                this._input = this._current.ToString();
                this._status = Status.Return;
                this.ValidateResult(this._current);
            }
            catch
            {
                this._status = Status.Error;
            }
        }
    }
}
